//===--- DiagnosticEngine.cpp - Diagnostic Display Engine -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file defines the DiagnosticEngine class, which manages any diagnostics
//  emitted by Swift.
//
//===----------------------------------------------------------------------===//

#include "polarphp/ast/DiagnosticEngine.h"
#include "polarphp/ast/AstContext.h"
#include "polarphp/ast/AstPrinter.h"
#include "polarphp/ast/Decl.h"
#include "polarphp/ast/DiagnosticSuppression.h"
#include "polarphp/ast/Module.h"
#include "polarphp/ast/Pattern.h"
#include "polarphp/ast/PrintOptions.h"
#include "polarphp/ast/SourceFile.h"
#include "polarphp/ast/TypeRepr.h"
#include "polarphp/basic/SourceMgr.h"
#include "polarphp/global/Config.h"
#include "polarphp/parser/Lexer.h" // bad dependency
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"

namespace polar {

namespace {
enum class DiagnosticOptions {
   /// No options.
      none,

   /// The location of this diagnostic points to the beginning of the first
   /// token that the parser considers invalid.  If this token is located at the
   /// beginning of the line, then the location is adjusted to point to the end
   /// of the previous token.
   ///
   /// This behavior improves experience for "expected token X" diagnostics.
      PointsToFirstBadToken,

   /// After a fatal error subsequent diagnostics are suppressed.
      Fatal,
};

struct StoredDiagnosticInfo {
   DiagnosticKind kind : 2;
   bool pointsToFirstBadToken : 1;
   bool isFatal : 1;

   constexpr StoredDiagnosticInfo(DiagnosticKind k, bool firstBadToken,
                                  bool fatal)
      : kind(k), pointsToFirstBadToken(firstBadToken), isFatal(fatal) {}

   constexpr StoredDiagnosticInfo(DiagnosticKind k, DiagnosticOptions opts)
      : StoredDiagnosticInfo(k,
                             opts == DiagnosticOptions::PointsToFirstBadToken,
                             opts == DiagnosticOptions::Fatal) {}
};

// Reproduce the DiagIDs, as we want both the size and access to the raw ids
// themselves.
enum LocalDiagID : uint32_t {
#define DIAG(KIND, ID, Options, Text, Signature) ID,

#include "polarphp/ast/DiagnosticsAllDefs.h"

   NumDiags
};
} // end anonymous namespace

// TODO: categorization
static const constexpr StoredDiagnosticInfo storedDiagnosticInfos[] = {
#define ERROR(ID, Options, Text, Signature)                                    \
  StoredDiagnosticInfo(DiagnosticKind::Error, DiagnosticOptions::Options),
#define WARNING(ID, Options, Text, Signature)                                  \
  StoredDiagnosticInfo(DiagnosticKind::Warning, DiagnosticOptions::Options),
#define NOTE(ID, Options, Text, Signature)                                     \
  StoredDiagnosticInfo(DiagnosticKind::Note, DiagnosticOptions::Options),
#define REMARK(ID, Options, Text, Signature)                                   \
  StoredDiagnosticInfo(DiagnosticKind::Remark, DiagnosticOptions::Options),

#include "polarphp/ast/DiagnosticsAllDefs.h"
};
static_assert(sizeof(storedDiagnosticInfos) / sizeof(StoredDiagnosticInfo) ==
              LocalDiagID::NumDiags,
              "array size mismatch");

static constexpr const char *const diagnosticStrings[] = {
#define ERROR(ID, Options, Text, Signature) Text,
#define WARNING(ID, Options, Text, Signature) Text,
#define NOTE(ID, Options, Text, Signature) Text,
#define REMARK(ID, Options, Text, Signature) Text,

#include "polarphp/ast/DiagnosticsAllDefs.h"

   "<not a diagnostic>",
};

static constexpr const char *const debugDiagnosticStrings[] = {
#define ERROR(ID, Options, Text, Signature) Text " [" #ID "]",
#define WARNING(ID, Options, Text, Signature) Text " [" #ID "]",
#define NOTE(ID, Options, Text, Signature) Text " [" #ID "]",
#define REMARK(ID, Options, Text, Signature) Text " [" #ID "]",

#include "polarphp/ast/DiagnosticsAllDefs.h"

   "<not a diagnostic>",
};

static constexpr const char *const fixItStrings[] = {
#define DIAG(KIND, ID, Options, Text, Signature)
#define FIXIT(ID, Text, Signature) Text,

#include "polarphp/ast/DiagnosticsAllDefs.h"

   "<not a fix-it>",
};

#define EDUCATIONAL_NOTES(DIAG, ...)                                           \
  static constexpr const char *const DIAG##_educationalNotes[] = {__VA_ARGS__, \
                                                                  nullptr};

#include "polarphp/ast/EducationalNotesDefs.h"

// NOTE: sadly, while GCC and Clang support array designators in C++, they are
// not part of the standard at the moment, so Visual C++ doesn't support them.
// This construct allows us to provide a constexpr array initialized to empty
// values except in the cases that EducationalNotes.def are provided, similar to
// what the C array would have looked like.
template<int N>
struct EducationalNotes {
   constexpr EducationalNotes() : value() {
      for (auto i = 0; i < N; ++i) value[i] = {};
#define EDUCATIONAL_NOTES(DIAG, ...)                                           \
  value[LocalDiagID::DIAG] = DIAG##_educationalNotes;

#include "polarphp/ast/EducationalNotesDefs.h"
   }

   const char *const *value[N];
};

static constexpr EducationalNotes<LocalDiagID::NumDiags> _EducationalNotes = EducationalNotes<LocalDiagID::NumDiags>();
static constexpr auto educationalNotes = _EducationalNotes.value;

DiagnosticState::DiagnosticState() {
   // Initialize our per-diagnostic state to default
   perDiagnosticBehavior.resize(LocalDiagID::NumDiags, Behavior::Unspecified);
}

static CharSourceRange toCharSourceRange(SourceManager &SM, SourceRange SR) {
//   return CharSourceRange(SM, SR.start, Lexer::getLocForEndOfToken(SM, SR.end));
}

static CharSourceRange toCharSourceRange(SourceManager &SM, SourceLoc Start,
                                         SourceLoc End) {
   return CharSourceRange(SM, Start, End);
}

/// Extract a character at \p Loc. If \p Loc is the end of the buffer,
/// return '\f'.
static char extractCharAfter(SourceManager &SM, SourceLoc Loc) {
   auto chars = SM.extractText({Loc, 1});
   return chars.empty() ? '\f' : chars[0];
}

/// Extract a character immediately before \p Loc. If \p Loc is the
/// start of the buffer, return '\f'.
static char extractCharBefore(SourceManager &SM, SourceLoc Loc) {
   // We have to be careful not to go off the front of the buffer.
   auto bufferID = SM.findBufferContainingLoc(Loc);
   auto bufferRange = SM.getRangeForBuffer(bufferID);
   if (bufferRange.getStart() == Loc)
      return '\f';
   auto chars = SM.extractText({Loc.getAdvancedLoc(-1), 1}, bufferID);
   assert(!chars.empty() && "Couldn't extractText with valid range");
   return chars[0];
}

InFlightDiagnostic &InFlightDiagnostic::highlight(SourceRange R) {
   assert(IsActive && "Cannot modify an inactive diagnostic");
   if (Engine && R.isValid())
      Engine->getActiveDiagnostic()
         .addRange(toCharSourceRange(Engine->SourceMgr, R));
   return *this;
}

InFlightDiagnostic &InFlightDiagnostic::highlightChars(SourceLoc Start,
                                                       SourceLoc End) {
   assert(IsActive && "Cannot modify an inactive diagnostic");
   if (Engine && Start.isValid())
      Engine->getActiveDiagnostic()
         .addRange(toCharSourceRange(Engine->SourceMgr, Start, End));
   return *this;
}

/// Add an insertion fix-it to the currently-active diagnostic.  The
/// text is inserted immediately *after* the token specified.
///
InFlightDiagnostic &
InFlightDiagnostic::fixItInsertAfter(SourceLoc L, StringRef FormatString,
                                     ArrayRef<DiagnosticArgument> Args) {
//   L = Lexer::getLocForEndOfToken(Engine->SourceMgr, L);
//   return fixItInsert(L, FormatString, Args);
}

/// Add a token-based removal fix-it to the currently-active
/// diagnostic.
InFlightDiagnostic &InFlightDiagnostic::fixItRemove(SourceRange R) {
   assert(IsActive && "Cannot modify an inactive diagnostic");
   if (R.isInvalid() || !Engine) return *this;

   // Convert from a token range to a CharSourceRange, which points to the end of
   // the token we want to remove.
   auto &SM = Engine->SourceMgr;
   auto charRange = toCharSourceRange(SM, R);

   // If we're removing something (e.g. a keyword), do a bit of extra work to
   // make sure that we leave the code in a good place, without extraneous white
   // space around its hole.  Specifically, check to see there is whitespace
   // before and after the end of range.  If so, nuke the space afterward to keep
   // things consistent.
   if (extractCharAfter(SM, charRange.getEnd()) == ' ' &&
       isspace(extractCharBefore(SM, charRange.getStart()))) {
      charRange = CharSourceRange(charRange.getStart(),
                                  charRange.getByteLength() + 1);
   }
   Engine->getActiveDiagnostic().addFixIt(Diagnostic::FixIt(charRange, {}, {}));
   return *this;
}

InFlightDiagnostic &
InFlightDiagnostic::fixItReplace(SourceRange R, StringRef FormatString,
                                 ArrayRef<DiagnosticArgument> Args) {
   auto &SM = Engine->SourceMgr;
   auto charRange = toCharSourceRange(SM, R);

   Engine->getActiveDiagnostic().addFixIt(
      Diagnostic::FixIt(charRange, FormatString, Args));
   return *this;
}

InFlightDiagnostic &InFlightDiagnostic::fixItReplace(SourceRange R,
                                                     StringRef Str) {
   if (Str.empty())
      return fixItRemove(R);

   assert(IsActive && "Cannot modify an inactive diagnostic");
   if (R.isInvalid() || !Engine) return *this;

   auto &SM = Engine->SourceMgr;
   auto charRange = toCharSourceRange(SM, R);

   // If we're replacing with something that wants spaces around it, do a bit of
   // extra work so that we don't suggest extra spaces.
   // FIXME: This could probably be applied to structured fix-its as well.
   if (Str.back() == ' ') {
      if (isspace(extractCharAfter(SM, charRange.getEnd())))
         Str = Str.drop_back();
   }
   if (!Str.empty() && Str.front() == ' ') {
      if (isspace(extractCharBefore(SM, charRange.getStart())))
         Str = Str.drop_front();
   }

   return fixItReplace(R, "%0", {Str});
}

InFlightDiagnostic &
InFlightDiagnostic::fixItReplaceChars(SourceLoc Start, SourceLoc End,
                                      StringRef FormatString,
                                      ArrayRef<DiagnosticArgument> Args) {
   assert(IsActive && "Cannot modify an inactive diagnostic");
   if (Engine && Start.isValid())
      Engine->getActiveDiagnostic().addFixIt(
         Diagnostic::FixIt(toCharSourceRange(Engine->SourceMgr, Start, End),
                           FormatString, Args));
   return *this;
}

InFlightDiagnostic &InFlightDiagnostic::fixItExchange(SourceRange R1,
                                                      SourceRange R2) {
   assert(IsActive && "Cannot modify an inactive diagnostic");

   auto &SM = Engine->SourceMgr;
   // Convert from a token range to a CharSourceRange
   auto charRange1 = toCharSourceRange(SM, R1);
   auto charRange2 = toCharSourceRange(SM, R2);
   // Extract source text.
   auto text1 = SM.extractText(charRange1);
   auto text2 = SM.extractText(charRange2);

   Engine->getActiveDiagnostic().addFixIt(
      Diagnostic::FixIt(charRange1, "%0", {text2}));
   Engine->getActiveDiagnostic().addFixIt(
      Diagnostic::FixIt(charRange2, "%0", {text1}));
   return *this;
}

void InFlightDiagnostic::flush() {
   if (!IsActive)
      return;

   IsActive = false;
   if (Engine)
      Engine->flushActiveDiagnostic();
}

void Diagnostic::addChildNote(Diagnostic &&D) {
   assert(storedDiagnosticInfos[(unsigned) D.ID].kind == DiagnosticKind::Note &&
          "Only notes can have a parent.");
   assert(storedDiagnosticInfos[(unsigned) ID].kind != DiagnosticKind::Note &&
          "Notes can't have children.");
   ChildNotes.push_back(std::move(D));
}

bool DiagnosticEngine::isDiagnosticPointsToFirstBadToken(DiagID ID) const {
   return storedDiagnosticInfos[(unsigned) ID].pointsToFirstBadToken;
}

bool DiagnosticEngine::finishProcessing() {
   bool hadError = false;
   for (auto &Consumer : Consumers) {
      hadError |= Consumer->finishProcessing();
   }
   return hadError;
}

/// Skip forward to one of the given delimiters.
///
/// \param Text The text to search through, which will be updated to point
/// just after the delimiter.
///
/// \param Delim The first character delimiter to search for.
///
/// \param FoundDelim On return, true if the delimiter was found, or false
/// if the end of the string was reached.
///
/// \returns The string leading up to the delimiter, or the empty string
/// if no delimiter is found.
static StringRef
skipToDelimiter(StringRef &Text, char Delim, bool *FoundDelim = nullptr) {
   unsigned Depth = 0;
   if (FoundDelim)
      *FoundDelim = false;

   unsigned I = 0;
   for (unsigned N = Text.size(); I != N; ++I) {
      if (Text[I] == '{') {
         ++Depth;
         continue;
      }
      if (Depth > 0) {
         if (Text[I] == '}')
            --Depth;
         continue;
      }

      if (Text[I] == Delim) {
         if (FoundDelim)
            *FoundDelim = true;
         break;
      }
   }

   assert(Depth == 0 && "Unbalanced {} set in diagnostic text");
   StringRef Result = Text.substr(0, I);
   Text = Text.substr(I + 1);
   return Result;
}

/// Handle the integer 'select' modifier.  This is used like this:
/// %select{foo|bar|baz}2.  This means that the integer argument "%2" has a
/// value from 0-2.  If the value is 0, the diagnostic prints 'foo'.
/// If the value is 1, it prints 'bar'.  If it has the value 2, it prints 'baz'.
/// This is very useful for certain classes of variant diagnostics.
static void formatSelectionArgument(StringRef ModifierArguments,
                                    ArrayRef<DiagnosticArgument> Args,
                                    unsigned SelectedIndex,
                                    DiagnosticFormatOptions FormatOpts,
                                    llvm::raw_ostream &Out) {
   bool foundPipe = false;
   do {
      assert((!ModifierArguments.empty() || foundPipe) &&
             "Index beyond bounds in %select modifier");
      StringRef Text = skipToDelimiter(ModifierArguments, '|', &foundPipe);
      if (SelectedIndex == 0) {
         DiagnosticEngine::formatDiagnosticText(Out, Text, Args, FormatOpts);
         break;
      }
      --SelectedIndex;
   } while (true);

}

static bool isInterestingTypealias(Type type) {
   // Dig out the typealias declaration, if there is one.
   TypeAliasDecl *aliasDecl = nullptr;
   if (auto aliasTy = dyn_cast<TypeAliasType>(type.getPointer()))
      aliasDecl = aliasTy->getDecl();
   else
      return false;

   if (aliasDecl == type->getAstContext().getVoidDecl())
      return false;

   // The 'Swift.AnyObject' typealias is not 'interesting'.
   if (aliasDecl->getName() ==
       aliasDecl->getAstContext().getIdentifier("AnyObject") &&
       (aliasDecl->getParentModule()->isStdlibModule() ||
        aliasDecl->getParentModule()->isBuiltinModule())) {
      return false;
   }

   // Compatibility aliases are only interesting insofar as their underlying
   // types are interesting.
   if (aliasDecl->isCompatibilityAlias()) {
      auto underlyingTy = aliasDecl->getUnderlyingType();
      return isInterestingTypealias(underlyingTy);
   }

   // Builtin types are never interesting typealiases.
   if (type->is<BuiltinType>()) return false;

   return true;
}

/// Decide whether to show the desugared type or not.  We filter out some
/// cases to avoid too much noise.
static bool shouldShowAKA(Type type, StringRef typeName) {
   // Canonical types are already desugared.
   if (type->isCanonical())
      return false;

   // Don't show generic type parameters.
   if (type->getCanonicalType()->hasTypeParameter())
      return false;

   // Only show 'aka' if there's a typealias involved; other kinds of sugar
   // are easy enough for people to read on their own.
   if (!type.findIf(isInterestingTypealias))
      return false;

   // If they are textually the same, don't show them.  This can happen when
   // they are actually different types, because they exist in different scopes
   // (e.g. everyone names their type parameters 'T').
   if (typeName == type->getCanonicalType()->getString())
      return false;

   return true;
}

/// If a type is part of an argument list which includes another, distinct type
/// with the same string representation, it should be qualified during
/// formatting.
static bool typeSpellingIsAmbiguous(Type type,
                                    ArrayRef<DiagnosticArgument> Args) {
   for (auto arg : Args) {
      if (arg.getKind() == DiagnosticArgumentKind::Type) {
         auto argType = arg.getAsType();
         if (argType && !argType->isEqual(type) &&
             argType->getWithoutParens().getString() == type.getString()) {
            return true;
         }
      }
   }
   return false;
}

/// Format a single diagnostic argument and write it to the given
/// stream.
static void formatDiagnosticArgument(StringRef Modifier,
                                     StringRef ModifierArguments,
                                     ArrayRef<DiagnosticArgument> Args,
                                     unsigned ArgIndex,
                                     DiagnosticFormatOptions FormatOpts,
                                     llvm::raw_ostream &Out) {
   const DiagnosticArgument &Arg = Args[ArgIndex];
   switch (Arg.getKind()) {
      case DiagnosticArgumentKind::Integer:
         if (Modifier == "select") {
            assert(Arg.getAsInteger() >= 0 && "Negative selection index");
            formatSelectionArgument(ModifierArguments, Args, Arg.getAsInteger(),
                                    FormatOpts, Out);
         } else if (Modifier == "s") {
            if (Arg.getAsInteger() != 1)
               Out << 's';
         } else {
            assert(Modifier.empty() && "Improper modifier for integer argument");
            Out << Arg.getAsInteger();
         }
         break;

      case DiagnosticArgumentKind::Unsigned:
         if (Modifier == "select") {
            formatSelectionArgument(ModifierArguments, Args, Arg.getAsUnsigned(),
                                    FormatOpts, Out);
         } else if (Modifier == "s") {
            if (Arg.getAsUnsigned() != 1)
               Out << 's';
         } else {
            assert(Modifier.empty() && "Improper modifier for unsigned argument");
            Out << Arg.getAsUnsigned();
         }
         break;

      case DiagnosticArgumentKind::String:
         if (Modifier == "select") {
            formatSelectionArgument(ModifierArguments, Args,
                                    Arg.getAsString().empty() ? 0 : 1, FormatOpts,
                                    Out);
         } else {
            assert(Modifier.empty() && "Improper modifier for string argument");
            Out << Arg.getAsString();
         }
         break;

      case DiagnosticArgumentKind::Identifier:
         assert(Modifier.empty() && "Improper modifier for identifier argument");
         Out << FormatOpts.OpeningQuotationMark;
         Arg.getAsIdentifier().printPretty(Out);
         Out << FormatOpts.ClosingQuotationMark;
         break;

//      case DiagnosticArgumentKind::ObjCSelector:
//         assert(Modifier.empty() && "Improper modifier for selector argument");
//         Out << FormatOpts.OpeningQuotationMark << Arg.getAsObjCSelector()
//             << FormatOpts.ClosingQuotationMark;
//         break;

      case DiagnosticArgumentKind::ValueDecl:
         Out << FormatOpts.OpeningQuotationMark;
         Arg.getAsValueDecl()->getFullName().printPretty(Out);
         Out << FormatOpts.ClosingQuotationMark;
         break;

      case DiagnosticArgumentKind::Type: {
         assert(Modifier.empty() && "Improper modifier for Type argument");

         // Strip extraneous parentheses; they add no value.
         auto type = Arg.getAsType()->getWithoutParens();

         // If a type has an unresolved type, print it with syntax sugar removed for
         // clarity. For example, print `Array<_>` instead of `[_]`.
         if (type->hasUnresolvedType()) {
            type = type->getWithoutSyntaxSugar();
         }

         bool isAmbiguous = typeSpellingIsAmbiguous(type, Args);

         if (isAmbiguous && isa<OpaqueTypeArchetypeType>(type.getPointer())) {
            auto opaqueTypeDecl = type->castTo<OpaqueTypeArchetypeType>()->getDecl();

            llvm::SmallString<256> NamingDeclText;
            llvm::raw_svector_ostream OutNaming(NamingDeclText);
            auto namingDecl = opaqueTypeDecl->getNamingDecl();
            if (namingDecl->getDeclContext()->isTypeContext()) {
               auto selfTy = namingDecl->getDeclContext()->getSelfInterfaceType();
               selfTy->print(OutNaming);
               OutNaming << '.';
            }
            namingDecl->getFullName().printPretty(OutNaming);

            auto descriptiveKind = opaqueTypeDecl->getDescriptiveKind();

            Out << llvm::format(FormatOpts.OpaqueResultFormatString.c_str(),
                                type->getString().c_str(),
                                Decl::getDescriptiveKindName(descriptiveKind).data(),
                                NamingDeclText.c_str());

         } else {
            auto printOptions = PrintOptions();
            printOptions.FullyQualifiedTypes = isAmbiguous;
            std::string typeName = type->getString(printOptions);

            if (shouldShowAKA(type, typeName)) {
               llvm::SmallString<256> AkaText;
               llvm::raw_svector_ostream OutAka(AkaText);
               OutAka << type->getCanonicalType();
               Out << llvm::format(FormatOpts.AKAFormatString.c_str(),
                                   typeName.c_str(), AkaText.c_str());
            } else {
               Out << FormatOpts.OpeningQuotationMark << typeName
                   << FormatOpts.ClosingQuotationMark;
            }
         }
         break;
      }
      case DiagnosticArgumentKind::TypeRepr:
         assert(Modifier.empty() && "Improper modifier for TypeRepr argument");
         Out << FormatOpts.OpeningQuotationMark << Arg.getAsTypeRepr()
             << FormatOpts.ClosingQuotationMark;
         break;
      case DiagnosticArgumentKind::PatternKind:
         assert(Modifier.empty() && "Improper modifier for PatternKind argument");
         Out << Arg.getAsPatternKind();
         break;

      case DiagnosticArgumentKind::SelfAccessKind:
         if (Modifier == "select") {
            formatSelectionArgument(ModifierArguments, Args,
                                    unsigned(Arg.getAsSelfAccessKind()),
                                    FormatOpts, Out);
         } else {
            assert(Modifier.empty() &&
                   "Improper modifier for SelfAccessKind argument");
            Out << Arg.getAsSelfAccessKind();
         }
         break;

      case DiagnosticArgumentKind::ReferenceOwnership:
         if (Modifier == "select") {
            formatSelectionArgument(ModifierArguments, Args,
                                    unsigned(Arg.getAsReferenceOwnership()),
                                    FormatOpts, Out);
         } else {
            assert(Modifier.empty() &&
                   "Improper modifier for ReferenceOwnership argument");
            Out << Arg.getAsReferenceOwnership();
         }
         break;

      case DiagnosticArgumentKind::StaticSpellingKind:
         if (Modifier == "select") {
            formatSelectionArgument(ModifierArguments, Args,
                                    unsigned(Arg.getAsStaticSpellingKind()),
                                    FormatOpts, Out);
         } else {
            assert(Modifier.empty() &&
                   "Improper modifier for StaticSpellingKind argument");
            Out << Arg.getAsStaticSpellingKind();
         }
         break;

      case DiagnosticArgumentKind::DescriptiveDeclKind:
         assert(Modifier.empty() &&
                "Improper modifier for DescriptiveDeclKind argument");
         Out << Decl::getDescriptiveKindName(Arg.getAsDescriptiveDeclKind());
         break;

      case DiagnosticArgumentKind::DeclAttribute:
         assert(Modifier.empty() &&
                "Improper modifier for DeclAttribute argument");
         if (Arg.getAsDeclAttribute()->isDeclModifier())
            Out << FormatOpts.OpeningQuotationMark
                << Arg.getAsDeclAttribute()->getAttrName()
                << FormatOpts.ClosingQuotationMark;
         else
            Out << '@' << Arg.getAsDeclAttribute()->getAttrName();
         break;

      case DiagnosticArgumentKind::VersionTuple:
         assert(Modifier.empty() &&
                "Improper modifier for VersionTuple argument");
         Out << Arg.getAsVersionTuple().getAsString();
         break;
      case DiagnosticArgumentKind::LayoutConstraint:
         assert(Modifier.empty() && "Improper modifier for LayoutConstraint argument");
         Out << FormatOpts.OpeningQuotationMark << Arg.getAsLayoutConstraint()
             << FormatOpts.ClosingQuotationMark;
         break;
   }
}

/// Format the given diagnostic text and place the result in the given
/// buffer.
void DiagnosticEngine::formatDiagnosticText(
   llvm::raw_ostream &Out, StringRef InText, ArrayRef<DiagnosticArgument> Args,
   DiagnosticFormatOptions FormatOpts) {
   while (!InText.empty()) {
      size_t Percent = InText.find('%');
      if (Percent == StringRef::npos) {
         // Write the rest of the string; we're done.
         Out.write(InText.data(), InText.size());
         break;
      }

      // Write the string up to (but not including) the %, then drop that text
      // (including the %).
      Out.write(InText.data(), Percent);
      InText = InText.substr(Percent + 1);

      // '%%' -> '%'.
      if (InText[0] == '%') {
         Out.write('%');
         InText = InText.substr(1);
         continue;
      }

      // Parse an optional modifier.
      StringRef Modifier;
      {
         size_t Length = InText.find_if_not(isalpha);
         Modifier = InText.substr(0, Length);
         InText = InText.substr(Length);
      }

      if (Modifier == "error") {
         Out << StringRef("<<INTERNAL ERROR: encountered %error in diagnostic text>>");
         continue;
      }

      // Parse the optional argument list for a modifier, which is brace-enclosed.
      StringRef ModifierArguments;
      if (InText[0] == '{') {
         InText = InText.substr(1);
         ModifierArguments = skipToDelimiter(InText, '}');
      }

      // Find the digit sequence, and parse it into an argument index.
      size_t Length = InText.find_if_not(isdigit);
      unsigned ArgIndex;
      bool IndexParseFailed = InText.substr(0, Length).getAsInteger(10, ArgIndex);

      if (IndexParseFailed) {
         Out << StringRef("<<INTERNAL ERROR: unparseable argument index in diagnostic text>>");
         continue;
      }

      InText = InText.substr(Length);

      if (ArgIndex >= Args.size()) {
         Out << StringRef("<<INTERNAL ERROR: out-of-range argument index in diagnostic text>>");
         continue;
      }

      // Convert the argument to a string.
      formatDiagnosticArgument(Modifier, ModifierArguments, Args, ArgIndex,
                               FormatOpts, Out);
   }
}

static DiagnosticKind toDiagnosticKind(DiagnosticState::Behavior behavior) {
   switch (behavior) {
      case DiagnosticState::Behavior::Unspecified:
         llvm_unreachable("unspecified behavior");
      case DiagnosticState::Behavior::Ignore:
         llvm_unreachable("trying to map an ignored diagnostic");
      case DiagnosticState::Behavior::Error:
      case DiagnosticState::Behavior::Fatal:
         return DiagnosticKind::Error;
      case DiagnosticState::Behavior::Note:
         return DiagnosticKind::Note;
      case DiagnosticState::Behavior::Warning:
         return DiagnosticKind::Warning;
      case DiagnosticState::Behavior::Remark:
         return DiagnosticKind::Remark;
   }

   llvm_unreachable("Unhandled DiagnosticKind in switch.");
}

// A special option only for compiler writers that causes Diagnostics to assert
// when a failure diagnostic is emitted. Intended for use in the debugger.
llvm::cl::opt<bool> AssertOnError("swift-diagnostics-assert-on-error",
                                  llvm::cl::init(false));
// A special option only for compiler writers that causes Diagnostics to assert
// when a warning diagnostic is emitted. Intended for use in the debugger.
llvm::cl::opt<bool> AssertOnWarning("swift-diagnostics-assert-on-warning",
                                    llvm::cl::init(false));

DiagnosticState::Behavior DiagnosticState::determineBehavior(DiagID id) {
   auto set = [this](DiagnosticState::Behavior lvl) {
      if (lvl == Behavior::Fatal) {
         fatalErrorOccurred = true;
         anyErrorOccurred = true;
      } else if (lvl == Behavior::Error) {
         anyErrorOccurred = true;
      }

      assert((!AssertOnError || !anyErrorOccurred) && "We emitted an error?!");
      assert((!AssertOnWarning || (lvl != Behavior::Warning)) &&
             "We emitted a warning?!");
      previousBehavior = lvl;
      return lvl;
   };

   // We determine how to handle a diagnostic based on the following rules
   //   1) If current state dictates a certain behavior, follow that
   //   2) If the user provided a behavior for this specific diagnostic, follow
   //      that
   //   3) If the user provided a behavior for this diagnostic's kind, follow
   //      that
   //   4) Otherwise remap the diagnostic kind

   auto diagInfo = storedDiagnosticInfos[(unsigned) id];
   bool isNote = diagInfo.kind == DiagnosticKind::Note;

   //   1) If current state dictates a certain behavior, follow that

   // Notes relating to ignored diagnostics should also be ignored
   if (previousBehavior == Behavior::Ignore && isNote)
      return set(Behavior::Ignore);

   // Suppress diagnostics when in a fatal state, except for follow-on notes
   if (fatalErrorOccurred)
      if (!showDiagnosticsAfterFatalError && !isNote)
         return set(Behavior::Ignore);

   //   2) If the user provided a behavior for this specific diagnostic, follow
   //      that

   if (perDiagnosticBehavior[(unsigned) id] != Behavior::Unspecified)
      return set(perDiagnosticBehavior[(unsigned) id]);

   //   3) If the user provided a behavior for this diagnostic's kind, follow
   //      that
   if (diagInfo.kind == DiagnosticKind::Warning) {
      if (suppressWarnings)
         return set(Behavior::Ignore);
      if (warningsAsErrors)
         return set(Behavior::Error);
   }

   //   4) Otherwise remap the diagnostic kind
   switch (diagInfo.kind) {
      case DiagnosticKind::Note:
         return set(Behavior::Note);
      case DiagnosticKind::Error:
         return set(diagInfo.isFatal ? Behavior::Fatal : Behavior::Error);
      case DiagnosticKind::Warning:
         return set(Behavior::Warning);
      case DiagnosticKind::Remark:
         return set(Behavior::Remark);
   }

   llvm_unreachable("Unhandled DiagnosticKind in switch.");
}

void DiagnosticEngine::flushActiveDiagnostic() {
   assert(ActiveDiagnostic && "No active diagnostic to flush");
   if (TransactionCount == 0) {
      emitDiagnostic(*ActiveDiagnostic);
   } else {
      onTentativeDiagnosticFlush(*ActiveDiagnostic);
      TentativeDiagnostics.emplace_back(std::move(*ActiveDiagnostic));
   }
   ActiveDiagnostic.reset();
}

void DiagnosticEngine::emitTentativeDiagnostics() {
   for (auto &diag : TentativeDiagnostics) {
      emitDiagnostic(diag);
   }
   TentativeDiagnostics.clear();
}

Optional<DiagnosticInfo>
DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
   auto behavior = state.determineBehavior(diagnostic.getID());
   if (behavior == DiagnosticState::Behavior::Ignore)
      return None;

   // Figure out the source location.
   SourceLoc loc = diagnostic.getLoc();
   if (loc.isInvalid() && diagnostic.getDecl()) {
      const Decl *decl = diagnostic.getDecl();
      // If a declaration was provided instead of a location, and that declaration
      // has a location we can point to, use that location.
      loc = decl->getLoc();

      if (loc.isInvalid()) {
         // There is no location we can point to. Pretty-print the declaration
         // so we can point to it.
         SourceLoc ppLoc = PrettyPrintedDeclarations[decl];
         if (ppLoc.isInvalid()) {
            class TrackingPrinter : public StreamPrinter {
               SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries;

            public:
               TrackingPrinter(
                  SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries,
                  raw_ostream &OS) :
                  StreamPrinter(OS), Entries(Entries) {}

               void printDeclLoc(const Decl *D) override {
                  Entries.push_back({D, OS.tell()});
               }
            };
            SmallVector<std::pair<const Decl *, uint64_t>, 8> entries;
            llvm::SmallString<128> buffer;
            llvm::SmallString<128> bufferName;
            {
               // Figure out which declaration to print. It's the top-most
               // declaration (not a module).
               const Decl *ppDecl = decl;
               auto dc = decl->getDeclContext();

               // FIXME: Horrible, horrible hackaround. We're not getting a
               // DeclContext everywhere we should.
               if (!dc) {
                  return None;
               }

               while (!dc->isModuleContext()) {
                  switch (dc->getContextKind()) {
                     case DeclContextKind::Module:
                        llvm_unreachable("Not in a module context!");
                        break;

                     case DeclContextKind::FileUnit:
                     case DeclContextKind::TopLevelCodeDecl:
                        break;

                     case DeclContextKind::ExtensionDecl:
                        ppDecl = cast<ExtensionDecl>(dc);
                        break;

                     case DeclContextKind::GenericTypeDecl:
                        ppDecl = cast<GenericTypeDecl>(dc);
                        break;

                     case DeclContextKind::SerializedLocal:
                     case DeclContextKind::Initializer:
                     case DeclContextKind::AbstractClosureExpr:
                     case DeclContextKind::AbstractFunctionDecl:
                     case DeclContextKind::SubscriptDecl:
                     case DeclContextKind::EnumElementDecl:
                        break;
                  }

                  dc = dc->getParent();
               }

               // Build the module name path (in reverse), which we use to
               // build the name of the buffer.
               SmallVector<StringRef, 4> nameComponents;
               while (dc) {
                  nameComponents.push_back(cast<ModuleDecl>(dc)->getName().str());
                  dc = dc->getParent();
               }

               for (unsigned i = nameComponents.size(); i; --i) {
                  bufferName += nameComponents[i - 1];
                  bufferName += '.';
               }

               if (auto value = dyn_cast<ValueDecl>(ppDecl)) {
                  bufferName += value->getBaseName().userFacingName();
               } else if (auto ext = dyn_cast<ExtensionDecl>(ppDecl)) {
                  bufferName += ext->getExtendedType().getString();
               }

               // Pretty-print the declaration we've picked.
               llvm::raw_svector_ostream out(buffer);
               TrackingPrinter printer(entries, out);
               ppDecl->print(printer, PrintOptions::printForDiagnostics());
            }

            // Build a buffer with the pretty-printed declaration.
            auto bufferID = SourceMgr.addMemBufferCopy(buffer, bufferName);
            auto memBufferStartLoc = SourceMgr.getLocForBufferStart(bufferID);

            // Go through all of the pretty-printed entries and record their
            // locations.
            for (auto entry : entries) {
               PrettyPrintedDeclarations[entry.first] =
                  memBufferStartLoc.getAdvancedLoc(entry.second);
            }

            // Grab the pretty-printed location.
            ppLoc = PrettyPrintedDeclarations[decl];
         }

         loc = ppLoc;
      }
   }

   return DiagnosticInfo(
      diagnostic.getID(), loc, toDiagnosticKind(behavior),
      diagnosticStringFor(diagnostic.getID(), getPrintDiagnosticNames()),
      diagnostic.getArgs(), getDefaultDiagnosticLoc(), /*child note info*/ {},
      diagnostic.getRanges(), diagnostic.getFixIts(), diagnostic.isChildNote());
}

void DiagnosticEngine::emitDiagnostic(const Diagnostic &diagnostic) {
   if (auto info = diagnosticInfoForDiagnostic(diagnostic)) {
      SmallVector<DiagnosticInfo, 1> childInfo;
      TinyPtrVector<DiagnosticInfo *> childInfoPtrs;
      auto childNotes = diagnostic.getChildNotes();
      for (unsigned idx = 0; idx < childNotes.size(); ++idx) {
         if (auto child = diagnosticInfoForDiagnostic(childNotes[idx])) {
            childInfo.push_back(*child);
            childInfoPtrs.push_back(&childInfo[idx]);
         }
      }
      info->ChildDiagnosticInfo = childInfoPtrs;

      SmallVector<std::string, 1> educationalNotePaths;
      if (useDescriptiveDiagnostics) {
         auto associatedNotes = educationalNotes[(uint32_t) diagnostic.getID()];
         while (associatedNotes && *associatedNotes) {
            SmallString<128> notePath(getDiagnosticDocumentationPath());
            llvm::sys::path::append(notePath, *associatedNotes);
            educationalNotePaths.push_back(notePath.str());
            associatedNotes++;
         }
         info->EducationalNotePaths = educationalNotePaths;
      }

      for (auto &consumer : Consumers) {
         consumer->handleDiagnostic(SourceMgr, *info);
      }
   }

   // For compatibility with DiagnosticConsumers which don't know about child
   // notes. These can be ignored by consumers which do take advantage of the
   // grouping.
   for (auto &childNote : diagnostic.getChildNotes())
      emitDiagnostic(childNote);
}

const char *DiagnosticEngine::diagnosticStringFor(const DiagID id,
                                                  bool printDiagnosticName) {
   if (printDiagnosticName) {
      return debugDiagnosticStrings[(unsigned) id];
   }
   return diagnosticStrings[(unsigned) id];
}

const char *InFlightDiagnostic::fixItStringFor(const FixItID id) {
   return fixItStrings[(unsigned) id];
}

void DiagnosticEngine::setBufferIndirectlyCausingDiagnosticToInput(
   SourceLoc loc) {
   // If in the future, nested BufferIndirectlyCausingDiagnosticRAII need be
   // supported, the compiler will need a stack for
   // bufferIndirectlyCausingDiagnostic.
   assert(bufferIndirectlyCausingDiagnostic.isInvalid() &&
          "Buffer should not already be set.");
   bufferIndirectlyCausingDiagnostic = loc;
   assert(bufferIndirectlyCausingDiagnostic.isValid() &&
          "Buffer must be valid for previous assertion to work.");
}

void DiagnosticEngine::resetBufferIndirectlyCausingDiagnostic() {
   bufferIndirectlyCausingDiagnostic = SourceLoc();
}

DiagnosticSuppression::DiagnosticSuppression(DiagnosticEngine &diags)
   : diags(diags) {
   consumers = diags.takeConsumers();
}

DiagnosticSuppression::~DiagnosticSuppression() {
   for (auto consumer : consumers)
      diags.addConsumer(*consumer);
}

bool DiagnosticSuppression::isEnabled(const DiagnosticEngine &diags) {
   return diags.getConsumers().empty();
}

BufferIndirectlyCausingDiagnosticRAII::BufferIndirectlyCausingDiagnosticRAII(
   const SourceFile &SF)
   : Diags(SF.getAstContext().Diags) {
   auto id = SF.getBufferID();
   if (!id)
      return;
   auto loc = SF.getAstContext().SourceMgr.getLocForBufferStart(*id);
   if (loc.isValid())
      Diags.setBufferIndirectlyCausingDiagnosticToInput(loc);
}

void DiagnosticEngine::onTentativeDiagnosticFlush(Diagnostic &diagnostic) {
   for (auto &argument : diagnostic.Args) {
      if (argument.getKind() != DiagnosticArgumentKind::String)
         continue;

      auto content = argument.getAsString();
      if (content.empty())
         continue;

      auto I = TransactionStrings.insert(content).first;
      argument = DiagnosticArgument(StringRef(I->getKeyData()));
   }
}

} // polar